/* -*- mode: c++ -*- */
/****************************************************************************
 *****                                                                  *****
 *****                   Classification: UNCLASSIFIED                   *****
 *****                    Classified By:                                *****
 *****                    Declassify On:                                *****
 *****                                                                  *****
 ****************************************************************************
 *
 *
 * Developed by: Naval Research Laboratory, Tactical Electronic Warfare Div.
 *               EW Modeling & Simulation, Code 5773
 *               4555 Overlook Ave.
 *               Washington, D.C. 20375-5339
 *
 * License for source code can be found at:
 * https://github.com/USNavalResearchLaboratory/simdissdk/blob/master/LICENSE.txt
 *
 * The U.S. Government retains all rights to use, duplicate, distribute,
 * disclose, or release this software.
 *
 */
#include <string.h>
#include "simNotify/Notify.h"
#include "simCore/Calc/Vec3.h"
#include "simCore/Calc/Angle.h"
#include "simCore/Time/TimeClass.h"
#include "simCore/Calc/MagneticVariance.h"

namespace simCore {

/** @name GeoMagConstants */
/** @{ */
/** WMM GeoMag Constants */
static const int GEO_MAG_MAX_DEG = 12;

static const double K_COEFF[13][13]=
{
  {0, 0, 0.3333333333333333, 0.2666666666666667, 0.2571428571428571, 0.253968253968254, 0.2525252525252525, 0.2517482517482518, 0.2512820512820513, 0.2509803921568627, 0.2507739938080495, 0.2506265664160401, 0.2505175983436853},
  {0, 0, 0, 0.2, 0.2285714285714286, 0.2380952380952381, 0.2424242424242424, 0.2447552447552448, 0.2461538461538462, 0.2470588235294118, 0.2476780185758514, 0.2481203007518797, 0.2484472049689441},
  {0, 0, -1, 0, 0.1428571428571429, 0.1904761904761905, 0.2121212121212121, 0.2237762237762238, 0.2307692307692308, 0.2352941176470588, 0.238390092879257, 0.2406015037593985, 0.2422360248447205},
  {0, 0, 0, -0.3333333333333333, 0, 0.1111111111111111, 0.1616161616161616, 0.1888111888111888, 0.2051282051282051, 0.2156862745098039, 0.2229102167182663, 0.2280701754385965, 0.2318840579710145},
  {0, 0, 0, 0, -0.2, 0, 0.09090909090909091, 0.1398601398601399, 0.1692307692307692, 0.1882352941176471, 0.2012383900928793, 0.2105263157894737, 0.2173913043478261},
  {0, 0, 0, 0, 0, -0.1428571428571429, 0, 0.07692307692307693, 0.1230769230769231, 0.1529411764705883, 0.173374613003096, 0.1879699248120301, 0.1987577639751553},
  {0, 0, 0, 0, 0, 0, -0.1111111111111111, 0, 0.06666666666666667, 0.1098039215686275, 0.1393188854489164, 0.1604010025062657, 0.1759834368530021},
  {0, 0, 0, 0, 0, 0, 0, -0.09090909090909091, 0, 0.05882352941176471, 0.09907120743034056, 0.1278195488721804, 0.1490683229813665},
  {0, 0, 0, 0, 0, 0, 0, 0, -0.07692307692307693, 0, 0.05263157894736842, 0.09022556390977443, 0.1180124223602485},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, -0.06666666666666667, 0, 0.04761904761904762, 0.08281573498964803},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -0.05882352941176471, 0, 0.04347826086956522},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -0.05263157894736842, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -0.04761904761904762}
};

static const double FN_COEFF[13] = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
static const double FM_COEFF[13] = {0, 1, 2, 3, 4, 5, 6, 7, 8,  9, 10, 11, 12};

static double SNORM_COEFF[169] =
{
  1, 1, 1.5, 2.5, 4.375, 7.875, 14.4375, 26.8125, 50.2734375, 94.9609375, 180.42578125, 344.44921875, 660.1943359375,
  0, 1, 1.732050807568877, 3.061862178478973, 5.533985905294664, 10.16658128379447, 18.90312474169284, 35.46960351395967,
  67.03125, 127.4034668742654, 243.286073807146, 466.3864469286422, 897.027461585248, 0, 0, 0.8660254037844386, 1.936491673103709,
  3.913118960624632, 7.685213074469698, 14.94423226950786, 28.96080999601013, 56.08236740361225, 108.6500416151266,
  210.6919203039643, 409.0479733748778, 795.1298606974663, 0, 0, 0, 0.7905694150420949, 2.091650066335189, 4.706212649254174,
  9.962821513005238, 20.47838513683391, 41.41957332816817, 82.98283999356998, 165.2803404594231, 327.9680080977904, 649.2208126530203,
  0, 0, 0, 0, 0.739509972887452, 2.218529918662355, 5.456862079070717, 12.34893087477617, 26.73621961783537, 56.37573837168898,
  116.8708495356794, 239.5139682335957, 486.9156094897652, 0, 0, 0, 0, 0, 0.7015607600201138, 2.326813808623285, 6.174465437388084,
  14.8305862683341, 33.69094768709671, 73.91561532231577, 158.4235988680796, 334.0213524451883, 0, 0, 0, 0, 0, 0, 0.6716932893813961,
  2.421824596249696, 6.865227429317255, 17.39793057467611, 41.32008511485578, 94.11764230125077, 208.2989101194602, 0, 0, 0, 0, 0, 0,
  0, 0.6472598492877496, 2.506826616960176, 7.533524925473754, 20.04318533977205, 49.60435294616063, 117.0538822714901, 0, 0, 0, 0, 0,
  0, 0, 0, 0.6267066542400439, 2.583977731709147, 8.1825961504123, 22.76003806863561, 58.52694113574506, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0.6090493921755237, 2.65478475211798, 8.814924839887254, 25.5432512332168, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.5936279171365733,
  2.72034486491732, 9.432470636269008, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.579979473934679, 2.78148384397026, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0.5677680121268564
};

//  SCHMIDT UNNORMALIZED GAUSS COEFFICIENTS

/// WMM 1985
static const double WMM1985_C[13][13] =
{
  {0, -29879.8, -3105.9, 3259.75, 4085.375, -1704.15, 768.0750000000001, 2061.88125, 925.0312499999999, 541.27734375, -613.44765625, 792.2332031249999, -1188.3498046875},
  {5490.5, -1903.3, 5266.820095655442, -6745.282379189177, 4326.470180759367, 3588.803193179448, 1206.019358520003, -2153.004933297352, 341.859375, 1388.697788929492, -1143.444546893586, -373.1091575429138, 0},
  {-3791.632422849029, -270.1999259807449, 1469.385302601057, 2404.541710492875, 1404.809706864243, 1954.349684837644, 766.6391154257532, 20.27256699720709, 67.2988408843347, 97.78503745361398, 526.7298007599109, -818.0959467497555, 79.51298606974663},
  {-950.0958339820252, 547.252546819108, -236.5383689805948, 663.6039669863344, -887.9054531592877, -440.9721252351161, -1876.995573050187, 520.1509824755814, -497.034879938018, -1012.390647921554, -909.041872526827, 688.7328170053598, -194.7662437959061},
  {1257.321597682947, -965.3664475860967, 151.6446298093012, -221.1874328906369, 121.6493905399859, -349.418462189321, 18.00764486093337, -100.026340085687, -243.2995985223019, 535.5695145310452, -245.4287840249267, 47.90279364671915, 243.4578047448826},
  {441.2296277166799, 1138.948577636409, -728.5217181045462, -159.2904481599571, 64.19280954184042, -31.71054635290914, 47.23432031505269, 42.60381151797778, 1.48305862683341, -111.1801273674191, 340.0118304826526, -63.36943954723186, 167.0106762225942},
  {-232.5084343228219, 1313.598016489741, 675.4792985817551, -278.8456522405136, -9.307255234493141, 13.97122041913304, -68.31120753008798, 16.95277217374787, 32.2665689177911, -17.39793057467611, 132.2242723675385, -37.64705692050031, -124.9793460716761},
  {-2841.115241468169, -750.0849788966623, -18.43054662315052, 266.7369068951653, 114.2276105916796, -48.43649192499392, -4.983900839515672, -2.847943336866099, 16.29437301024114, 48.9679120155794, 12.02591120386323, 79.36696471385702, -46.82155290859605},
  {254.71875, -1132.863821552968, 207.0978666408408, -647.016514751616, 180.9331524736761, 52.17572846281113, -40.86127385645087, -6.831102531216478, -5.953713215280417, 3.87596659756372, 15.54693268578337, 34.14005710295341, 0},
  {-2649.99211098472, 1716.670657519001, 746.8455599421299, -281.8786918584449, -215.622065197419, 158.3211682295526, 74.58189676219017, -14.98707084391305, 1.400813602003704, -2.923437082442514, 7.433397305930344, -6.170447387921078, -12.7716256166084},
  {291.9432885685752, 84.27676812158575, 413.2008511485577, 654.4767573998045, -325.2287074181894, -20.66004255742789, -32.06909654363528, 30.27560575652551, -1.32739237605899, -3.621130294533097, -0.1187255834273147, 6.256793189309835, 0},
  {606.3023810072348, 818.0959467497555, -360.7648089075695, -670.6391110540679, 110.8965192076557, -9.411764230125078, -119.0504470707855, -9.104015227454244, -13.22238725983088, -4.08051729737598, 0.4059856317542753, 2.029928158771376, 1.947038690779182},
  {269.1082384755744, 477.0779164184797, 1623.052031632551, -827.7565361326008, 100.2064057335565, 41.65978202389204, -11.70538822714901, 5.852694113574507, 2.55432512332168, -13.20545889077661, 1.112593537588104, 0.3974376084887994, -0.1135536024253713}
};

static const double WMM1985_CD[13][13] =
{
  {0, 21.9, -16.8, 20.75, -5.25, 11.025, 44.75625, -2.68125, 10.0546875, 0, 0, 0, 0},
  {-31.5, 10.6, 3.117691453623979, -6.123724356957945, 0.5533985905294664, -5.083290641897235, 0, -28.37568281116774, 0, 0, 0, 0, 0},
  {-16.80089283341811, -17.23390553531033, 8.05403625519528, -1.161895003862225, -37.95725391805893, -9.222255689363637, 26.89961808511415, -34.75297199521215, 39.25765718252858, 0, 0, 0, 0},
  {18.67735928872173, 2.517439175034821, -10.27740239554723, 1.897366596101028, -3.555805112769821, -10.35366782835918, -1.992564302601048, 22.52622365051731, 4.141957332816817, 0, 0, 0, 0},
  {7.194181676883063, 14.08722825824868, 5.229125165837973, 0.4437059837324712, -6.877442747853304, 1.99667692679612, -2.182744831628287, 0, 5.347243923567074, 0, 0, 0, 0},
  {-9.149923155415022, 4.611127844681819, 1.411863794776252, 5.324471804789653, -0.9821850640281593, 0, 5.584353140695884, 3.70467926243285, -4.44917588050023, 0, 0, 0, 0},
  {13.23218731918499, -31.38288776596651, -13.94795011820733, -23.46450694000408, -1.6287696660363, 0, 1.209047920886513, -4.359284273249453, -0.6865227429317256, 0, 0, 0, 0},
  {0, 34.75297199521215, 40.95677027366783, 32.10722027441803, 5.557018893649276, 1.937459676999757, 0.2589039397150998, -0.7767118191452995, 0.5013653233920351, 0, 0, 0, 0},
  {-40.21875, -84.12355110541839, 4.141957332816817, -29.40984157961891, 5.932234507333641, -13.73045485863451, 2.256143955264158, 0.9400599813600659, -1.378754639328097, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
};


/// WMM 1990
static const double WMM1990_C[13][13] =
{
  {0, -29780.5, -3201.45, 3282.25, 4084.0625, -1640.3625, 851.8125, 2040.43125, 1151.26171875, 341.859375, -595.4050781249999, 447.783984375, -858.2526367187501},
  {5407.2, -1851.7, 5303.885982937415, -6872.962032031749, 4343.625537065781, 3580.669928152412, 1204.129046045834, -2202.662378216895, 154.171875, 1210.332935305521, -632.5437918985796, -652.941025700099, 89.7027461585248},
  {-3946.131354884173, -332.8135626743598, 1465.228380662892, 2414.417818025704, 1265.893983762069, 1894.405022856781, 896.6539361704715, 37.64905299481317, -67.2988408843347, -97.78503745361398, 948.1136413678395, -1022.619933437194, 397.5649303487331},
  {-872.3245346486592, 564.8746210443518, -278.5966618608342, 639.2544290030379, -882.0488329735491, -521.4483615373624, -1806.25954030785, 618.4472311323842, -484.6090079395676, -887.9163879311988, -925.5699065727692, 1049.497625912929, 454.4545688571142},
  {1380.176084780489, -910.5827821373518, 190.9676510564027, -219.2647069611295, 102.9397882259333, -360.0674057989003, 2.182744831628287, 58.03997511144799, -467.883843312119, 603.220400577072, -420.7350583284458, 47.90279364671915, 194.7662437959061},
  {414.7965163788143, 1142.791184173644, -727.5804755746952, -149.9726225015752, 68.33201802595909, -26.09806027274824, 35.8329326527986, 48.77827695536586, 32.62728979033503, -107.8110325987095, 288.2708997570315, -174.2659587548876, -66.80427048903766},
  {-277.8759337028847, 1228.415892553546, 697.3975059103667, -306.6756488437743, -3.257539332072599, 16.52365491878235, -64.48255578061402, 24.46042842212193, 39.13179634710836, -24.35710280454655, 132.2242723675385, 28.23529269037523, -229.1288011314062},
  {-2787.91083619723, -773.2536268934704, 2.047838513683391, 245.7437244080457, 110.5229313292467, -52.06922881936847, -4.401366975156697, 1.229793713646724, 7.520479850880527, 47.46120703048465, 34.07341507761248, -14.88130588384819, 105.3484940443411},
  {650.203125, -1082.389690889717, 273.3691839659099, -537.3980143184909, 198.729855995677, 67.2792288073091, -47.62970572224334, -5.7030305535844, -4.386946579680307, 2.067182185367317, 24.5477884512369, 20.48403426177205, -35.11616468144704},
  {-2790.135924546411, 1553.695595096311, 788.3369799389149, -377.7174470903161, -215.622065197419, 158.3211682295526, 67.04837183671641, -20.67182185367317, 1.2790037235686, -3.34977165696538, 9.822703582836526, -9.696417323875981, 20.43460098657344},
  {632.5437918985796, 252.8303043647572, 429.7288851945, 666.1638423533724, -295.6624612892631, -16.52803404594231, -34.07341507761248, 31.09386537156674, -2.123827801694384, -3.858581461387726, 0.4155395419956013, 6.528827675801567, 1.886494127253802},
  {0, 409.0479733748778, -524.7488129564647, -526.9307301139106, 174.2659587548876, -65.88234961087554, -84.32740000847308, -34.14005710295341, -11.45940229185343, -2.992379351409052, 0.3479876843608074, 1.739938421804037, 1.112593537588104},
  {627.9192231096736, 556.5909024882263, 843.9870564489264, -730.3734142346477, 100.2064057335565, 41.65978202389204, -128.7592704986392, 70.23232936289408, -5.108650246643361, -12.26221182714971, 1.668890306382156, 0.3406608072761138, 0.1135536024253713}
};

static const double WMM1990_CD[13][13] =
{
  {0, 16, -17.55, 5.25, -3.5, 13.3875, 11.55, 13.40625, 0, 0, 0, 0, 0},
  {-13.8, 9.300000000000001, 6.408587988004846, -23.27015255644019, 5.533985905294664, 0, 0, 0, -73.734375, 0, 0, 0, 0},
  {-22.17025033688163, -12.90377851638814, 1.55884572681199, 0, -28.95708030862228, 0, 22.41634840426179, -26.06472899640912, 0, 0, 0, 0, 0},
  {9.491772753284815, 1.549193338482967, -8.933434389975673, -4.585302607244151, 1.673320053068151, -12.70677415298627, 0, 30.71757770525087, 0, 0, 0, 0, 0},
  {18.26215348747239, 14.47854015431114, 5.856620185738529, 0, -4.732863826479693, 0, 0, 33.34211336189566, -56.14606119745428, 0, 0, 0, 0},
  {0, -16.13894745638637, 5.647455179105008, 2.662235902394826, 0.4209364560120683, 2.104682280060342, 0, -6.174465437388084, 0, 0, 0, 0, 0},
  {-11.3418748450157, -8.966539361704715, 0, -12.55078278186265, 0, 0, 0, 0, 6.865227429317255, 0, 0, 0, 0},
  {21.2817621083758, 23.1686479968081, 0, 0, 0, 0.9687298384998786, 0, 0, 0, 0, 0, 0, 0},
  {26.8125, -44.8658939228898, 20.70978666408409, 8.020865885350611, 7.415293134167051, 0, -1.754778631872123, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
};


/// WMM 1995
static const double WMM1995_C[13][13] =
{
  {0, -29682.1, -3292.05, 3297, 4112.5, -1649.8125, 988.96875, 2091.375, 1241.75390625, 275.38671875, -523.234765625, 585.563671875, -1188.3498046875},
  {5315.6, -1782.2, 5332.291616181546, -6961.449848989792, 4332.557565255193, 3598.969774463242, 1240.04498305505, -2415.479999300653, 227.90625, 955.5260015569902, -802.8440435635818, -746.2183150858275, 807.3247154267233},
  {-4086.081060135738, -362.518234024166, 1459.859023159428, 2414.611467193015, 1138.326305645705, 1830.617754338682, 957.9252884754537, 2.896080999601013, -84.12355110541839, 43.46001664605066, 589.9373768511001, -1472.57270414956, -79.51298606974663},
  {-799.4522148008598, 582.8839936042162, -329.2721613650325, 605.8133427467573, -876.1922127878106, -574.6285644739346, -1684.713117849186, 606.160200050284, -397.6279039504144, -854.7232519337708, -710.7054639755193, 393.5616097173485, -324.6104063265101},
  {1435.515943833436, -903.5391680082275, 208.7466766202518, -226.3640027008491, 84.15623491459203, -361.1766707582315, -2.728431039535359, 74.09358524865701, -441.1476236942836, 546.844662205383, -362.2996335606061, -143.7083809401574, 389.5324875918122},
  {444.2796021018183, 1211.189580536424, -706.4025186530515, -131.3369711848114, 73.24294334609989, -16.34636570846865, 38.39242784228421, 53.71784930527632, 38.55952429766867, -77.48917968032242, 177.3974767735579, 15.84235988680796, 66.80427048903766},
  {-287.3274960737311, 1110.356457624434, 691.4198130025636, -301.7644729726106, 6.980441425869856, 22.36738653640049, -61.12408933370704, 22.2807862854972, 24.71481874554212, -41.75503337922266, 115.6962383215962, -65.88234961087554, 104.1494550597301},
  {-2699.236827412331, -709.5398449022481, 32.76541621893426, 246.9786174955233, 101.8786797169034, -57.15506047149284, -4.401366975156697, -1.553423638290599, -12.28345042310486, 51.22796949322153, 14.03022973784043, -39.68348235692851, 46.82155290859605},
  {998.765625, -1093.606164370439, 260.9433119674595, -545.4188802038415, 180.9331524736761, 48.05659200522078, -47.62970572224334, -5.515018557312387, -5.327006561040373, -1.291988865854573, 33.54864421669043, 29.58804948922629, -23.41077645429803},
  {-2522.588644110454, 1586.290607580849, 904.5129559299128, -422.8180377876673, -229.0984442722576, 161.8007543444878, 58.00814192614791, -20.93021962684409, 1.583528419656362, -3.958821049140904, 9.557225107624729, -2.644477451966176, 7.662975369965041},
  {778.5154361828672, 358.1762645167394, 479.3129873323269, 654.4767573998045, -251.3130920958736, -28.92405958039904, -58.12523748533894, 18.81997114594829, -4.247655603388768, -3.917944253101383, 0.356176750281944, 5.984758702818104, 1.886494127253802},
  {139.9159340785926, 409.0479733748778, -1180.684829152046, -335.319555527034, 301.0048378493513, 18.82352846025016, -64.48565883000882, -54.62409136472546, -5.288954903932353, -5.984758702818104, 0.7539733161150827, 2.435913790525652, 1.112593537588104},
  {269.1082384755744, 1113.181804976453, 519.3766501224162, -1460.746828469296, 233.8149467116318, 104.1494550597301, -93.64310581719211, 35.11616468144704, 2.55432512332168, -12.26221182714971, -1.112593537588104, 0.5109912109141708, 0.3406608072761138}
};

static const double WMM1995_CD[13][13] =
{
  {0, 17.6, -20.55, 2, 5.25, 7.0875, 5.775, -8.043749999999999, 5.02734375, 0, 0, 0, 0},
  {-18, 13.2, 6.928203230275509, -20.20829037796122, 6.08738449582413, 5.083290641897235, -5.670937422507851, -39.01656386535564, 0, 0, 0, 0, 0},
  {-25.28794179050561, -6.235382907247958, -0.2598076211353316, -0.9682458365518543, -26.6092089322475, -10.75929830425758, 4.483269680852358, -14.48040499800506, 22.4329469614449, 0, 0, 0, 0},
  {12.24744871391589, 4.260281680828159, -9.961174629530396, -6.719840027857806, 0.6274950199005567, -8.000561503732095, 20.921925177311, 10.23919256841696, 12.42587199845045, 0, 0, 0, 0},
  {7.194181676883063, 3.913118960624632, 5.229125165837973, -0.8874119674649424, -3.327794877993534, 0, 0, 16.05361013720902, -34.75708550318598, 0, 0, 0, 0},
  {5.083290641897235, 11.52781961170455, 2.823727589552504, 3.771500861726004, 0.4209364560120683, 1.473277596042239, -0.9307255234493141, 0.6174465437388084, 7.415293134167051, 0, 0, 0, 0},
  {13.23218731918499, -22.41634840426179, -4.981410756502619, -3.819803455349502, 2.559495189485614, 1.74640255239163, -0.2686773157525584, 0, 2.746090971726902, 0, 0, 0, 0},
  {10.6408810541879, 0, 14.33486959578374, -7.4093585248657, 0.6174465437388084, -1.453094757749818, -0.2589039397150998, -0.5825338643589747, -2.256143955264158, 0, 0, 0, 0},
  {26.8125, -16.82471022108367, 4.141957332816817, 21.3889756942683, -1.48305862683341, -8.924795658112432, -2.256143955264158, -0.6893773196640484, 0.06267066542400439, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
};


/// WMM 2000
static const double WMM2000_C[13][13] =
{
  {0, -29616, -3400.05, 3306, 4077.9375, -1668.7125, 1065.4875, 2075.2875, 1171.37109375, 541.27734375, -396.93671875, 1136.682421875, -990.29150390625},
  {5194.5, -1722.7, 5317.742389397967, -7016.257181984565, 4351.373117333194, 3574.569979382136, 1289.193107383452, -2621.20369968162, 489.328125, 1082.929468431256, -1386.730620700732, -513.0250916215064, -179.4054923170496},
  {-4303.799846647146, -405.2132864307388, 1452.844217388774, 2432.039892250948, 980.6276115325328, 1696.895046842909, 1107.367611170532, 63.71378199122228, -476.7001229307041, 217.3000832302533, 337.107072486343, -981.7151360997066, -238.5389582092399},
  {-688.0004315042251, 567.3920602193866, -384.6120204179792, 573.0047120225104, -839.7975016335784, -632.9856013246864, -1628.921317376357, 731.0783493849708, -273.3691839659099, -813.2318319369858, -611.5372596998654, 852.7168210542551, 324.6104063265101},
  {1512.438347917032, -891.7998111263537, 252.8804930199244, -223.8496687930317, 78.53595912064741, -374.4878502702056, -20.73607590046872, 90.14719538586601, -451.8421115414178, 428.4556116248362, -70.12250972140762, -311.3681587036745, 97.38312189795305},
  {426.9964139193677, 1335.690032342834, -635.3387076493135, -85.63525486036691, 73.80419195411598, -9.330758108267514, 39.78851612745818, 32.10722027441803, 127.5430419076733, -235.836633809677, 303.0540228214946, -269.3201180757354, 300.6192172006695},
  {-328.9143705054554, 914.587014893881, 629.6503196219311, -343.2366247735481, 0.4653627617246571, 28.88281144340003, -57.1610989263568, 20.34332660849745, 33.63961440365455, -34.79586114935222, 90.90418725268272, -56.47058538075046, -291.6184741672442},
  {-2209.756298919687, -709.5398449022481, 182.2576277178218, 288.9649824697623, 92.61698156082126, -66.84235885649161, -5.048626824444447, -0.9708897739316245, -19.55324761228937, 69.30842931435853, 44.09500774749851, 19.84174117846425, 70.23232936289408},
  {831.1875, -1166.513241995135, 347.9244159566126, -566.8078558981099, 229.8740871591786, 62.47356960678702, -38.85581256288272, -3.384215932896237, -4.762970572224334, -5.684751009760123, 37.63994229189657, 15.93202664804493, -35.11616468144704},
  {-2599.030724235013, 1510.23557845026, 995.7940799228397, -349.5295779044716, -289.7421501090317, 163.5405474019555, 37.66762462736877, -21.70541294635683, 1.948958054961676, -4.019725988358456, 6.106004929871354, -2.644477451966176, -25.5432512332168},
  {218.9574664264314, -147.484344212775, 644.59332779175, 560.980077771261, -391.7527612082736, -41.32008511485578, -48.10364481545292, 10.63737499553599, -6.106004929871354, -3.799218669674069, 0.05936279171365733, 6.256793189309835, -2.829741190880702},
  {-699.5796703929632, 286.3335813624144, -360.7648089075695, -550.8821269372701, 205.9506785285035, -56.47058538075046, -138.8921882492498, -36.41606090981698, -0.8814924839887255, -5.168655243342908, 0.8119712635085505, 2.435913790525652, 0.8344451531910778},
  {-897.027461585248, 556.5909024882263, 1428.285787836645, -1217.289023724413, -66.80427048903766, 0, -23.41077645429803, 0, 5.108650246643361, -8.489223572642107, -0.556296768794052, 0.5677680121268564, 0.2271072048507425}
};

static const double WMM2000_CD[13][13] =
{
  {0, 14.7, -20.4, 0.75, -7, -7.0875, 17.325, -10.725, -15.08203125, 0, 0, 0, 0},
  {-20.4, 11.1, -1.212435565298214, -13.16600736745958, 4.980587314765198, -2.033316256758894, 3.780624948338568, -28.37568281116774, 40.21875, 0, 0, 0, 0},
  {-37.23909236273086, -8.31384387633061, -1.55884572681199, 1.742842505793338, -29.7397041007472, -19.21303268617425, 25.40519485816336, -5.792161999202026, -44.8658939228898, 0, 0, 0, 0},
  {19.59591794226543, -2.517439175034821, -10.51457322005986, -6.640783086353597, 4.601630145937416, -12.70677415298627, 15.94051442080838, 22.52622365051731, 12.42587199845045, 0, 0, 0, 0},
  {12.72816758217773, 2.739183272437242, 7.739105245440199, -0.369754986443726, -2.366431913239846, -1.99667692679612, -0.5456862079070718, 4.939572349910467, -5.347243923567074, 0, 0, 0, 0},
  {0, 16.13894745638637, 10.8242890932846, 6.877442747853301, 0, 1.192653292034194, -0.6980441425869856, 0, 7.415293134167051, 0, 0, 0, 0},
  {-5.670937422507851, -25.40519485816336, -8.966539361704715, -5.456862079070717, -0.2326813808623285, 1.276217249824653, 0.5373546315051169, -0.4843649192499393, 0, 0, 0, 0, 0},
  {49.65744491954354, 5.792161999202026, 14.33486959578374, 4.939572349910467, -1.852339631216425, -1.937459676999757, -0.06472598492877496, -0.1294519698575499, -1.504095970176105, 0, 0, 0, 0},
  {-33.515625, 5.608236740361225, -8.283914665633635, 0, 1.48305862683341, -0.6865227429317256, 0.7520479850880527, 0.1253413308480088, 0.06267066542400439, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
};


/// WMM 2005
static const double WMM2005_C[13][13] =
{
  {0, -29556.8, -3510.9, 3338.5, 4024.125, -1790.775, 1056.825, 2147.68125, 1251.80859375, 531.78125, -414.979296875, 964.4578124999999, -1584.46640625},
  {5079.8, -1671.7, 5277.385605581612, -7057.89850761188, 4416.674151015671, 3605.069723233519, 1317.547794495991, -2642.485461789996, 516.140625, 1261.294322055227, -1532.70226498502, -746.2183150858275, -358.8109846340992},
  {-4494.152230398965, -447.4753261354194, 1435.004094070815, 2414.224168858394, 826.8420363799849, 1603.903968641826, 1146.222615071253, -40.54513399441417, -650.5554618819022, 380.2751456529433, 337.107072486343, -695.3815547372922, 159.0259721394933},
  {-612.0662494779466, 521.4972075668287, -414.4164873650662, 532.8437857383719, -793.5720351675707, -642.3980266231947, -1506.378612766392, 788.4178277681057, -285.7950559643604, -580.8798799549899, -429.7288851945, 557.5456137662437, 519.3766501224162},
  {1557.817032340448, -884.3648851011669, 304.9625796716705, -225.3286887388066, 73.9509972887452, -373.3785853108744, -81.30724497815369, 153.1267428472245, -486.5991970446037, 287.5162656956138, 0, -23.95139682335957, -146.0746828469296},
  {431.0630464328855, 1381.801310789652, -578.8641558582634, -43.26133341391593, 72.68169473808379, -9.892006716283605, 33.97148160589996, 58.6574216551868, 148.305862683341, -363.8622350206445, 229.1384074991789, 15.84235988680796, 367.4234876897072},
  {-383.7334322563647, 817.4495051420799, 633.6354482271331, -345.9650558130834, -0.2326813808623285, 33.85334178482236, -57.96713087361448, 13.80440019862327, 63.16009234971874, -22.61730974707895, 16.52803404594231, -65.88234961087554, -104.1494550597301},
  {-2181.38061610852, -648.7221439106268, 147.4443729852042, 313.6628442193147, 67.91911981126891, -63.93616934099198, -3.301025231367523, 1.165067728717949, -29.07918875673804, 66.29501934416905, 42.0906892135213, 34.72304706231244, 46.82155290859605},
  {750.75, -1177.729715475857, 397.6279039504144, -529.3771484331404, 238.7724389201791, 52.86225120574287, -32.33806335878627, -0.1253413308480088, -3.258874602048228, -17.31265080245128, 31.91212498660797, 40.9680685235441, -17.55808234072352},
  {-2560.809684172734, 1401.585536835134, 1045.583783918982, -377.7174470903161, -272.8966762654833, 139.1834445974089, 21.84722228387389, -20.41342408050226, 3.654296353053142, -5.542349468797266, -0.265478475211798, 0, -7.662975369965041},
  {583.8865771371503, 42.13838406079287, 727.2334980214616, 560.980077771261, -480.4514995950525, -45.45209362634136, -68.14683015522496, -6.54607692032984, -6.106004929871354, -4.689660545378929, -1.365344209414118, 2.992379351409052, -0.9432470636269008},
  {139.9159340785926, 490.8575680498533, -262.3744064782323, -598.7849205839892, 142.5812389812717, -56.47058538075046, -133.9317529546337, -20.48403426177205, -11.45940229185343, -5.44068972983464, -0.6959753687216147, 2.377915843132183, -0.8344451531910778},
  {-358.8109846340992, 238.5389582092399, 1558.129950367249, -1265.98058467339, 200.412811467113, 62.48967303583804, 0, 0, 7.662975369965041, -8.489223572642107, -1.112593537588104, 0.4542144097014851, -0.05677680121268564},
};

static const double WMM2005_CD[13][13] =
{
  {0, 8, -22.65, 1, -10.9375, -22.05, -10.10625, 5.362500000000001, 5.02734375, 0, 0, 0, 0},
  {-20.9, 10.6, -13.50999629903724, -7.960841664045329, 15.49516053482506, 7.116606898656128, 7.561249896677136, -3.546960351395967, 20.109375, 0, 0, 0, 0},
  {-40.18357873559795, -12.6439708952528, -0.6928203230275509, -2.32379000772445, -27.39183272437242, -24.59268183830304, -4.483269680852358, -8.688242998803037, -22.4329469614449, 0, 0, 0, 0},
  {15.30931089239486, -13.55544171172596, -0.4743416490252569, -5.138701197773616, 12.96823041127817, -5.176833914179592, 22.91448947991205, 22.52622365051731, 12.42587199845045, 0, 0, 0, 0},
  {12.17476899164826, 6.260990336999412, 12.1315703847441, 0.0739509972887452, -2.810137896972317, 0.2218529918662355, -11.45941036604851, 7.4093585248657, -8.020865885350611, 0, 0, 0, 0},
  {0, 13.06486222659849, 9.883046563433766, 10.64894360957931, -0.7717168360221253, -0.5612486080160911, -1.396088285173971, 3.087232718694042, 2.966117253666821, 0, 0, 0, 0},
  {-11.3418748450157, -28.39404131206493, -3.985128605202096, -2.728431039535359, -0.6980441425869856, 0.4701853025669772, 0.9403706051339544, -0.9687298384998786, 2.746090971726902, 0, 0, 0, 0},
  {21.2817621083758, 11.58432399840405, 4.095677027366783, 3.70467926243285, -4.939572349910467, -0.4843649192499393, 0.06472598492877496, 0.3883559095726498, -1.754778631872123, 0, 0, 0, 0},
  {-13.40625, 5.608236740361225, 12.42587199845045, 10.69448784713415, 1.48305862683341, -1.373045485863451, 1.00273064678407, 0.2506826616960176, 0.2506826616960176, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
};


/// WMM 2010
static const double WMM2010_C[13][13] =
{
  {0, -29496.6, -3594.9, 3350.25, 3992.625, -1818.3375, 1051.05, 2158.40625, 1226.671875, 512.7890625, -360.8515625, 1033.34765625, -1452.4275390625},
  {4944.4, -1586.3, 5241.358948784179, -7122.503799577786, 4476.441198792853, 3631.502834571385, 1296.754357280129, -2663.767223898371, 542.953125, 1197.592588618094, -1532.70226498502, -699.5796703929632, -179.4054923170496},
  {-4689.873971654249, -498.9172351202151, 1445.049988754714, 2385.564092096459, 652.3169307361261, 1539.348178816281, 1135.761652482597, -136.1158069812476, -813.1943273523776, 369.4101414914306, 189.6227282735679, -859.0007440872433, 238.5389582092399},
  {-490.5103209923313, 487.8022524548242, -424.2195481115882, 501.2210091366882, -746.928238688296, -664.046604809764, -1408.742961938941, 927.6708466985763, -231.9496106377417, -431.510767966564, -181.8083745053654, 557.5456137662437, 649.2208126530203},
  {1584.933563276392, -826.4507244839223, 343.6581058988716, -228.5825326195114, 66.11219157613822, -361.6203767419639, -124.4164554028124, 171.6501391593887, -516.0090386242226, 174.7647889522358, -23.37416990713588, -119.7569841167979, -292.1493656938591},
  {453.4295252572334, 1451.736749767326, -556.2743351418434, 0, 70.78748068602948, -5.472173928156888, 30.71394227382736, 64.21444054883607, 170.5517420858422, -417.7677513199992, 184.7890383057894, 79.21179943403982, 300.6192172006695},
  {-393.184994627211, 659.0406430852966, 612.7135230498221, -361.7899558423885, 7.213122806732184, 36.94313091597678, -52.32490724281076, 4.117101813624483, 74.83097897955808, -12.17855140227328, -12.39602553445673, -75.29411384100062, -20.82989101194602},
  {-2053.690043458265, -611.0730909158137, 133.1095033894204, 307.4883787819265, 43.22125806171658, -67.08454131611659, -2.135957502649573, 3.171573261509973, -35.34625529913848, 63.28160937397954, 44.09500774749851, 19.84174117846425, 58.52694113574506},
  {737.34375, -1121.647348072245, 492.8929226052013, -465.2102213503354, 247.6707906811795, 48.05659200522078, -27.0737274631699, 1.065401312208075, -2.318814620688162, -21.96381071952775, 25.36604806627813, 40.9680685235441, -23.41077645429803},
  {-2611.77107092244, 1249.475478573956, 1062.180351917696, -405.9053162761607, -249.3130128845157, 139.1834445974089, 15.82040234349489, -15.76226416342579, 4.263345745228666, -6.151398860972789, -2.65478475211798, 0.8814924839887255, -10.21730049328672},
  {681.2010066600087, -21.06919203039644, 776.8176001592885, 514.2317379569893, -532.1924303206736, -41.32008511485578, -78.16842282511098, -16.3651923008246, -5.30956950423596, -4.927111712233558, -1.662158167982405, 1.904241405442124, 1.886494127253802},
  {93.27728938572844, 695.3815547372922, -196.7808048586742, -431.1251428204723, 142.5812389812717, -37.64705692050031, -124.0108823654016, -29.58804948922629, -18.51134216376324, -5.168655243342908, -1.043963053082422, 2.20392200095178, -2.225187075176208},
  {-807.3247154267233, 238.5389582092399, 1363.363706571343, -1217.289023724413, 167.0106762225942, 124.9793460716761, 0, 5.852694113574507, 7.662975369965041, -8.489223572642107, -0.556296768794052, 0.5109912109141708, 0},
};

static const double WMM2010_CD[13][13] =
{
  {0, 11.6, -18.15, 1, -7.875, -7.875, -2.8875, 2.68125, -5.02734375, 0, 0, 0, 0},
  {-25.9, 16.5, -7.62102355330306, -12.55363493176379, 12.72816758217773, 6.099948770276682, -3.780624948338568, -3.546960351395967, 6.703125, -12.74034668742654, 0, 0, 0},
  {-38.97114317029974, -10.21909976465638, 1.645448267190433, -5.615825852000755, -34.0441349574343, -13.83338353404546, -1.494423226950786, -17.37648599760608, -33.64942044216735, 0, -21.06919203039644, 0, 79.51298606974663},
  {22.3515939028965, -7.552317525104463, -2.055480479109447, -6.08738449582413, 9.621590305141869, -4.706212649254174, 19.92564302601048, 26.62190067788409, 8.283914665633635, 24.89485199807099, 33.05606809188462, 32.79680080977904, 64.92208126530203},
  {6.08738449582413, 10.56542119368651, 8.157435258707237, -0.5916079783099616, -1.552970943063649, 1.99667692679612, -9.27666553442022, 4.939572349910467, -5.347243923567074, -22.55029534867559, 0, 0, -48.69156094897652},
  {4.066632513517788, 13.83338353404546, 5.647455179105008, 8.874119674649421, -0.4209364560120683, 0.7015607600201138, -0.6980441425869856, 1.852339631216425, 4.44917588050023, -10.10728430612901, -7.391561532231577, 0, 0},
  {-3.780624948338568, -31.38288776596651, -3.985128605202096, -3.27411724744243, 1.163406904311643, 0.6045239604432565, 1.141878591948373, -1.695277217374787, 2.059568228795176, 1.739793057467611, -8.264017022971157, 0, 0},
  {24.82872245977177, 8.688242998803037, -2.047838513683391, -1.234893087477617, -4.939572349910467, -0.7265473788749088, 0.1941779547863249, 0.3883559095726498, -1.504095970176105, -0.7533524925473755, 0, 0, 0},
  {-6.703125, 11.21647348072245, 16.56782933126727, 10.69448784713415, 1.48305862683341, -0.6865227429317256, 1.00273064678407, 0.1880119962720132, 0.1253413308480088, -1.033591092683659, -0.81825961504123, 0, 0},
  {0, -21.73000832302533, 0, -5.637573837168898, 3.369094768709671, 0, -1.506704985094751, 0.775193319512744, 0.1218098784351047, -0.1218098784351047, -0.530956950423596, 0, 0},
  {24.3286073807146, -21.06919203039644, 0, -11.68708495356794, -7.391561532231577, 0, -2.004318533977205, -1.63651923008246, 0, -0.05936279171365733, -0.1187255834273147, -0.272034486491732, 0},
  {0, 40.90479733748778, 0, 23.95139682335957, 0, 9.411764230125078, 0, -2.276003806863561, -0.8814924839887255, 0, -0.0579979473934679, 0, -0.278148384397026},
  {0, 0, 0, 0, 0, 20.82989101194602, 0, 0, 0, 0, 0, 0, 0.05677680121268564},
};


/// WMM 2015 v2, NOAA issued an out-of-cycle update for the WMM2015 model on Feb 4th, 2019
static const double WMM2015_C[13][13] =
{
  { 0, -29438.2, -3666.75, 3379.5, 3970.3125, -1834.0875, 1001.9625, 2190.58125, 1216.6171875, 522.28515625, -360.8515625, 1033.34765625, -1320.388671875 },
  { 4796.3, -1493.5, 5221.613647385999, -7200.275098911152, 4509.091749229443, 3660.985876652035, 1279.74155136915, -2692.142906709539, 596.5781272224267, 1121.150493643362, -1484.045072337585, -652.9410327761567, -89.702746381304 },
  { -4923.181288794894, -553.2170361810754, 1454.056674621205, 2369.491228863783, 460.9654169960417, 1473.255361743091, 1080.467998452161, -205.6217540356747, -947.7920045814464, 325.9501253850503, 42.13838468870372, -940.8103573694762, 397.5649270276024 },
  { -348.1337296930592, 477.345200976563, -424.8520131398041, 460.3485806686042, -701.957783182061, -664.9878542759783, -1286.200268509072, 1068.97172007165, -128.400678615645, -265.5450838968768, 99.16820760052701, 688.7328260657547, 779.0649802845364 },
  { 1567.778218650836, -738.0142414724402, 377.9611782508892, -244.038298325767, 51.5438466463817, -348.7529081507042, -154.9748874717777, 185.2339686420448, -553.4397450877832, 33.82544258698016, -58.43542672703998, -191.6111821042042, -438.2240514100517 },
  { 476.8126565259107, 1510.144384885328, -564.2749025314211, 35.49647920108949, 70.5770139829586, 5.402017968874565, 31.64466917260839, 56.18763715475358, 197.2468006859477, -444.7205090384048, 133.0481130322395, 95.05416305003143, 300.6192197289146 },
  { -379.9528091952721, 490.1708208745625, 588.8027565366858, -366.1554559632494, 18.84719266898, 41.57781703930952, -47.22004099940968, -7.265474140607077, 79.63663991489088, -1.73979310114907, -28.92406076565476, -65.8823530136879, 20.82989136449347 },
  { -1925.99947080801, -564.7358033374165, 122.8703126519138, 302.5488154486731, 21.61062967490522, -67.08454456493867, -1.877053695793295, 3.818833381096703, -40.86127581211679, 65.54166849719417, 44.09500963656564, 4.960435669087716, 70.23232915619042 },
  { 677.0156275220797, -1026.307318570442, 550.8803308348641, -387.6751837571429, 240.2555015873949, 41.19136547321942, -23.06280597984506, 1.504096042163808, -1.316084036893332, -23.51419799272921, 19.63823189494025, 38.69206778174915, -23.41077638539681 },
  { -2777.395541071056, 1162.55544720668, 979.1974968697333, -383.3550159857752, -232.4675388155298, 137.4436549907765, 7.53352511462002, -10.07751342545537, 5.176919992398584, -6.334113873052385, -4.778612847540734, -1.762985142663167, -12.77162569796474 },
  { 802.8440555268903, -84.27676937740745, 760.2895916040403, 514.2317551979519, -583.933384974829, -24.79205208484694, -84.18138203344348, -23.72953020638614, -2.920263406830448, -5.223926030823686, -2.137060648973326, 1.088138063919368, 1.886494167381882 },
  { -0, 859.0007610764784, -196.7808074473585, -263.4653753932808, 110.89652355837, -18.82352943248226, -104.169149050842, -34.14005980742572, -22.91880685462117, -5.440690319596839, -1.333952954525591, 2.029928409060682, -2.503335536136426 },
  { -897.0274638130398, 238.5389562165614, 1168.597470426805, -1071.214347891238, 100.2064065763049, 145.8092395514543, -11.7053881926984, 17.5580822890476, 5.108650279185896, -8.48922375321847, -0.5562967858080947, 0.4542144303617152, -0 },
};

static const double WMM2015_CD[13][13] =
{
  { 0, 7, -16.5, 6, -3.5, -2.3625, -11.55, -8.043749999999999, -5.02734375, -9.49609375, 0, -0, 0 },
  { -30.2, 9, -10.73871516694636, -17.45261441733014, -4.980587351873465, 6.099948697559625, -9.451562417792838, -7.093920702791934, 13.40625004994217, -12.74034651867457, -0, 0, 0 },
  { -51.26870466800199, -14.98223970872355, 0.2598076250067668, 3.872983375063392, -25.43527343356767, -6.14817052370617, -1.494423234373667, -8.688243128267946, -11.21647342699937, -0, -21.06919234435186, -0, -0 },
  { 19.90210416011332, -1.549193350025357, -1.581138865425397, -8.696263759839681, 10.87658066909034, 0.4706212698343795, 15.94051455936882, 18.43054689778706, 20.70978687349113, 33.1931354871096, 33.05606920017567, 0, 0 },
  { -2.213594378610429, 22.69609014072192, 7.94827048895063, -2.588284982242983, -2.958039979706267, 2.662235940081712, -8.7309795758748, 1.234893124280299, -2.673621956945813, -22.55029505798678, -11.687085345408, -0, -48.69156126778353 },
  { 2.033316232519875, 17.67599025565524, -0, 7.321148835224706, -0.420936465107109, 0.982185085249921, 0, -3.704679372840896, 5.932234607096171, 0, -14.78312367024884, -15.84236050833857, -0 },
  { 5.670937450675702, -22.41634851560499, -11.95538591952662, 2.1827448939687, 0.4653627819501234, 0.8732013271583583, 0.806031994300023, -2.179642242182123, 2.746091031547961, 5.219379303447211, -0, 0, 0 },
  { 21.2817621083758, 14.48040521377991, -16.3827083535885, -2.469786248560597, -6.791912183541642, 0.2421824713535692, 0.1294519790202272, 0.4530819265707953, -0.2506826736939681, 0, -2.004318619843893, -0, -0 },
  { -26.81250009988435, 33.64942028099809, -4.141957374698227, 16.04173174167488, -2.966117303548085, -3.432613789434952, 1.25341336846984, 0.06267066842349202, 0.2506826736939681, -0, -1.636519324578355, -0, 0 },
  { -38.22103955602371, 10.86500417950168, -33.1931354871096, 16.91272129349008, 3.369094765442461, -0, -0.7533525114620021, 1.291988900699407, 0.1218098821740843, -0.1827148232611265, -0.2654784915300407, -0.8814925713315834, -0 },
  { 0, 21.06919234435186, -33.05606920017567, 11.687085345408, -7.391561835124418, 4.132008680807823, -0, -0.8182596622891772, 0.5309569830600814, -0, -0, -0, -0 },
  { 0, 40.90479814649897, 0, 23.95139776302553, -0, -0, 4.960435669087716, -0, -0.8814925713315834, -0, -0.0579979545445909, -0.0579979545445909, -0 },
  { -0, 0, -64.92208169037804, 48.69156126778353, -0, 0, -0, 0, 0, -0, 0, -0.0567768037952144, -0.0567768037952144 },
};

// WMM 2020
static const double WMM2020_C[13][13] =
{
{0, -29404.5, -3750, 3409.75, 3951.0625, -1845.9, 951.4312500000001, 2161.0875, 1186.453125, 474.8046875, -342.808984375, 1033.34765625, -1320.388671875},
{4652.9, -1450.7, 5164.975585134524, -7290.293846958433, 4479.208225118203, 3691.485620139833, 1240.04498921442, -2724.065549872103, 656.9062524471665, 1044.708414531315, -1508.373680080824, -652.9410327761567, -89.702746381304},
{-5181.603273134957, -636.3554761832409, 1452.151418704489, 2393.891024126682, 337.3108569190051, 1443.283030440024, 1090.928961092776, -240.3747265487465, -981.4414248624445, 315.0851212055487, -21.06919234435186, -1022.619953662474, 397.5649270276024},
{-251.6850710709716, 468.243690045164, -429.2001450197239, 415.6023507770655, -647.1565498108749, -662.1641266569719, -1210.48282435207, 1157.028777472188, -16.56782949879291, -116.1759742048836, 280.9765882014932, 787.1232297894338, 843.9870619749145},
{1560.584036920352, -619.8380479810951, 417.9116957085095, -258.902449223791, 35.42252875698254, -335.4417284502956, -197.5384129041674, 195.1131136362872, -564.1342329155666, -62.01331140946364, -105.183768108672, -215.5625798672297, -584.2987352134023},
{484.9459214559902, 1601.598421425457, -570.8636003091024, 71.4366643921926, 69.52467282019083, 9.611382619945655, 31.41198778163333, 39.51657997696955, 226.9079737214285, -448.0896038038474, 44.3493710107465, 47.52708152501572, 233.8149486780447},
{-361.0496843596864, 373.6058085934166, 525.0406982992105, -351.4219279289607, 20.94132518775555, 45.7423156765263, -43.45855835934291, -17.43713793745698, 94.05361783051767, 19.13772411263977, -37.18807812727041, -65.8823530136879, 62.4896740934804},
{-1823.137620617527, -486.541615183005, 47.10028651656694, 290.1998842058701, -13.58382436708328, -65.87363220817083, -1.229793800692158, 6.343146971991135, -41.36264115950473, 67.04837352011818, 38.08205377703396, -4.960435669087716, 58.52694096349202},
{563.0625020975713, -858.0602171654515, 530.170543961373, -315.487390919606, 220.9757391143324, 24.71481928393165, -17.2971044848838, 1.754778715857776, -0.188012005270476, -24.03099355300897, 11.45563527204848, 31.864055820264, -11.7053881926984},
{-2968.500738851175, 1206.015463924686, 813.2318194341852, -287.5162619893314, -208.8838754574326, 135.7038618896275, 3.013410045848008, -3.875966702098221, 5.907779285443089, -7.247687989358018, -6.371483796720978, -5.2889554279895, -12.77162569796474},
{827.1726632701294, -42.13838468870372, 578.4812110030742, 560.9800965795838, -635.6743178206999, -4.132008680807823, -84.18138203344348, -27.82082851783202, -0.2654784915300407, -5.223926030823686, -2.31514903638777, 0.5440690319596839, 0.943247083690941},
{-0, 1063.524751808973, -163.9840062061321, -95.8055910521021, 95.05416305003143, -18.82352943248226, -84.32740637449118, -36.41606379458744, -26.4447771399475, -5.440690319596839, -1.507946818159364, 1.797936590882318, -3.059632321944521},
{-1076.432956575648, 397.5649270276024, 843.9870619749145, -876.4481028201035, 33.40213552543496, 145.8092395514543, -11.7053881926984, 35.11616457809521, 5.108650279185896, -8.48922375321847, -0, 0.283884018976072, -0.1703304113856432},
};

static const double WMM2020_CD[13][13] =
{
{0, 6.7, -17.25, 7, -4.8125, -2.3625, -8.6625, -2.68125, -5.02734375, -9.49609375, 0, -0, 0},
{-25.1, 7.7, -12.29756091698696, -18.98354550656963, -8.854377514441715, 6.099948697559625, -7.56124993423427, -10.6408810541879, 6.703125024971087, -25.48069303734914, -0, -46.63864519829691, -0},
{-52.30793516802905, -20.69800745887242, -1.90525591671629, 6.584071737607766, -23.47871393867785, -5.379649208242899, 7.472116171868332, -2.896081042755982, -5.608236713499683, -0, -0, -0, -0},
{17.45261441733014, -1.936491687531696, 0.8696263759839682, -9.644947079094919, 11.29491069482458, 0.4706212698343795, 13.94795023944772, 14.33486980938994, 20.70978687349113, 33.1931354871096, 33.05606920017567, 0, 0},
{1.106797189305214, 27.00052102947952, 7.739105476083509, -4.141255971588773, -4.067304972096117, 2.662235940081711, -7.639607128890449, 2.469786248560597, -2.673621956945813, -16.91272129349008, -11.687085345408, -0, -0},
{1.016658116259938, 19.21303288658178, -4.235591428509416, 6.655589850204279, 0.3507803875892575, 0.701560775178515, -0, -3.087232810700746, 5.932234607096171, -0, -14.78312367024884, -15.84236050833857, -0},
{1.890312483558568, -26.89961821872599, -13.94795023944772, 4.911176011429575, 0.2326813909750617, 0.6716933285833525, 0.537354662866682, -1.937459770828554, 3.432613789434952, 5.21937930344721, -0, 0, 0},
{17.73480175697983, 17.37648625653589, -14.33486980938994, -2.469786248560597, -7.409358745681791, 0.4843649427071385, 0.1941779685303408, 0.6472598951011361, 0, -0, -2.004318619843893, -0, -0},
{-20.10937507491326, 39.25765699449778, -8.283914749396454, 13.36810978472906, -4.449175955322128, -3.432613789434952, 1.002730694775872, 0.06267066842349202, 0.2506826736939681, -0, -1.636519324578354, -2.276003987161715, 0},
{-38.22103955602371, 21.73000835900336, -33.1931354871096, 22.55029505798678, 3.369094765442461, -0, -1.506705022924004, 1.291988900699407, 0.1218098821740843, -0.2436197643481687, -0.2654784915300407, -0.8814925713315834, -0},
{-0, 21.06919234435186, -49.58410380026351, 11.687085345408, -14.78312367024884, 4.132008680807823, -0, -0.8182596622891772, 0.5309569830600814, -0, -0, -0.272034515979842, -0},
{-0, 40.90479814649897, 0, 47.90279552605105, -0, 0, 4.960435669087716, -0, -0.8814925713315834, 0, -0, -0.0579979545445909, -0},
{-0, 0, -64.92208169037804, 48.69156126778353, -0, 0, -0, 5.852694096349202, -0, -0, 0, -0.0567768037952144, -0.0567768037952144},
};

static const double GEOMAG_A  = 6378.137;     ///< major radius (km) IAU66 ellipsoid
static const double GEOMAG_B  = 6356.7523142; ///< minor radius b=a*(1-f)
static const double GEOMAG_RE = 6371.2;       ///< "mean radius" for spherical harmonic expansion
static const double GEOMAG_A2 = GEOMAG_A * GEOMAG_A;
static const double GEOMAG_B2 = GEOMAG_B * GEOMAG_B;
static const double GEOMAG_C2 = GEOMAG_A2 - GEOMAG_B2;
static const double GEOMAG_A4 = GEOMAG_A2 * GEOMAG_A2;
static const double GEOMAG_B4 = GEOMAG_B2 * GEOMAG_B2;
static const double GEOMAG_C4 = GEOMAG_A4 - GEOMAG_B4;
/** @} */

////////////////////////////////////////////////////////////////

/** Encapsulates the variables needed for calculation of WMM variances */
class WorldMagneticModel::GeoMag
{
public:
  /** Constructor initializes all values */
  GeoMag()
    : epochYear_(1985),
      dec_(0),
      ct_(0),
      st_(0),
      r_(0),
      d_(0),
      ca_(0),
      sa_(0),
      aor_(0),
      ar_(0),
      br_(0),
      bt_(0),
      bp_(0),
      bpp_(0),
      otime_(-1000),
      oalt_(-1000),
      olat_(-1000),
      olon_(-1000),
      oyear_(0)
  {
    // clear all arrays
    memset(tc_, 0, 169 * sizeof(double));
    memset(dp_, 0, 169 * sizeof(double));
    memset(sp_, 0, 13 * sizeof(double));
    memset(cp_, 0, 13 * sizeof(double));
    memset(pp_, 0, 13 * sizeof(double));
    cp_[0] = 1.0;
    pp_[0] = 1.0;
  }

  /** Destructor */
  ~GeoMag() {}

  /** Calculates the magnetic variation in radians. */
  int calculateVariance(const simCore::Vec3& lla, int ordinalDay, int refYear, double& variance);

private:
  int epochYear_;
  double dec_;

  // WORLD MAGNETIC MODEL SPHERICAL HARMONIC COEFFICIENTS
  double tc_[13][13];
  double dp_[13][13];
  double sp_[13];
  double cp_[13];
  double pp_[13];
  double ct_, st_, r_, d_, ca_, sa_;
  double aor_, ar_, br_, bt_, bp_, bpp_;
  double otime_, oalt_, olat_, olon_;
  int oyear_;

  /** Track if we've issued a warning about the WMM bounds */
  static bool tooLateWarned_;
};

bool WorldMagneticModel::GeoMag::tooLateWarned_ = false;

// // // // // // // // // // // // // // // // // // // // // // //

//                    ********************************
//                    *      GEOMAG-C USER NOTES     *
//                    ********************************
//         PROGRAMMED BY:  JOHN M. QUINN  7/19/90

//        This routine was written using ANSI C keywords.  It compiled
//  and executed with no errors  under both Borland's Turbo C 2.01  and
//  Borland's Turbo C++.

// GENERAL INFORMATION

//         The  World Magnetic Model  Epoch 2000 (WMM-2000), the GEOMAG
//  algorithm, and magnetic products derived from them such as the
//  wall-sized Mercator and polar stereographic magnetic charts are
//  Defense Mapping Agency (DMA) standard products covered under
//  the 1993 DMA military specification: MIL-W-89500.

// Changes from previous versions of GEOMAG are:

// 1.   The GEOMAG algorithm for WMM-2000 is identical to that of
//      WMM-95.  The comments have been changed to reflect the fact
//      that the Defense Mapping Agency (DMA) has been reorganized into
//      into the National Imagery and Mapping Agency (NIMA).

// 2.   The previous version of the GEOMAG algorithm read in the
//      coefficient file WMM-95.DAT.  This version reads the file
//      WMM.COF.  The format is unchanged.


// INPUTS AND OUTPUTS

//        GEOMAG computes the main components of the geomagnetic field
// and their annual changes.  The program is designed to be used in
// demand mode.  The input parameters and valid entries are:

// 		      Latitude  -   -90.00 to  +90.00 degrees
// 		      Longitude -  -180.00 to +180.00 degrees
// 		      Altitude  -  Sea level to 1,000,000 meters
// 		      Date      -  1995.000 to 2000.000

// The altitude is  referenced to the  World Geodetic System 1984 (WGS 84)
// ellipsoid.  The seven computed magnetic components displayed are:

// 		  TI  -  Total Intensity of the geomagnetic field
// 		  HI  -  Horizontal Intensity of the geomagnetic field
// 		  X   -  North Component of the geomagnetic field
// 		  Y   -  East Component of the geomagnetic field
// 		  Z   -  Vertical Component of the geomagnetic field
// 		  DIP -  Geomagnetic Inclination
// 		  DEC -  Geomagnetic Declination (Magnetic Variation)

//        Annual change  in each of these  magnetic components is also
// displayed.  The annual change  is computed by subtracting  the main
// field values for the desired input date  from main field values
// one year later.  The output units are displayed using the abbreviations
// nT (nanoTesla), deg (degrees) and min (minutes) per year.

// As geomagnetic model data is only reliable for five years
// from the epoch date  of the model,  computing data for a date that
// exceeds  the life of the model  may produce  inaccurate results.
// Therefore,  when a date is entered that exceeds five years from the
// epoch date, an exception is raised.

// // // // // // // // // // // // // // // // // // // // // // //

int WorldMagneticModel::GeoMag::calculateVariance(const simCore::Vec3& lla, int ordinalDay, int refYear, double& variance)
{
  // determine appropriate epoch year
  if (refYear >= 1985 && refYear < 1990)
    epochYear_ = 1985;
  else if (refYear >= 1990 && refYear < 1995)
    epochYear_ = 1990;
  else if (refYear >= 1995 && refYear < 2000)
    epochYear_ = 1995;
  else if (refYear >= 2000 && refYear < 2005)
    epochYear_ = 2000;
  else if (refYear >= 2005 && refYear < 2010)
    epochYear_ = 2005;
  else if (refYear >= 2010 && refYear < 2015)
    epochYear_ = 2010;
  else if (refYear >= 2015 && refYear < 2020)
    epochYear_ = 2015;
  else
    // default to last updated WMM
    epochYear_ = 2020;

  // Warn users when their refYear is beyond the available model, but continue with the latest available year
  const auto maxYear = (epochYear_ + 5);
  if (refYear > maxYear || (refYear == maxYear && ordinalDay > 0))
  {
    if (!tooLateWarned_)
    {
      SIM_ERROR << "calculateVariance encountered a date (" << ordinalDay << " " << refYear << ") which is more than 5 years beyond the last available WMM (" <<
        epochYear_ << "). Proceeding with date clamped to: 00 " << maxYear << std::endl;
      tooLateWarned_ = true;
    }
    // Calculation extends to 5 years beyond the epoch date, so set day to zero and cap the year
    refYear = maxYear;
    ordinalDay = 0;
  }

  // convert time to year decimal fraction
  const double time = static_cast<double>(refYear) + (static_cast<double>(ordinalDay) / 365.25);

  const double dt = time - static_cast<double>(epochYear_);

  // convert alt from m to km
  const double altkm = lla.alt() / 1000.;

  // convert rad to deg
  const double dlat = simCore::RAD2DEG * lla.lat();
  const double dlon = simCore::RAD2DEG * lla.lon();

  // check for exact location
  if (altkm == oalt_ && dlat == olat_ && dlon == olon_ &&
    otime_ == time && oyear_ == epochYear_)
  {
    variance = dec_;
    return 0;
  }

  double *p = SNORM_COEFF;
  const double srlon = sin(lla.lon());
  const double srlat = sin(lla.lat());
  const double crlon = cos(lla.lon());
  const double crlat = cos(lla.lat());
  sp_[1] = srlon;
  cp_[1] = crlon;

  // CONVERT FROM GEODETIC COORDS. TO SPHERICAL COORDS. */
  if (altkm != oalt_ || dlat != olat_)
  {
    const double srlat2 = srlat * srlat;
    const double crlat2 = crlat * crlat;
    const double q = sqrt(GEOMAG_A2 - GEOMAG_C2 * srlat2);
    const double q1 = altkm * q;
    const double q2 = ((q1 + GEOMAG_A2) / (q1 + GEOMAG_B2)) * ((q1 + GEOMAG_A2) / (q1 + GEOMAG_B2));
    ct_ = srlat / sqrt(q2 * crlat2 + srlat2);
    st_ = sqrt(1.0 - (ct_ * ct_));
    const double r2 = (altkm * altkm) + 2.0 * q1 + (GEOMAG_A4 - GEOMAG_C4 * srlat2) / (q * q);
    r_ = sqrt(r2);
    d_ = sqrt(GEOMAG_A2 * crlat2 + GEOMAG_B2 * srlat2);
    ca_ = (altkm + d_) / r_;
    sa_ = GEOMAG_C2 * crlat * srlat / (r_ * d_);
  }

  if (dlon != olon_)
  {
    for (int m = 2; m <= GEO_MAG_MAX_DEG; m++)
    {
      sp_[m] = sp_[1] * cp_[m - 1] + cp_[1] * sp_[m - 1];
      cp_[m] = cp_[1] * cp_[m - 1] - sp_[1] * sp_[m - 1];
    }
  }

  aor_ = GEOMAG_RE / r_;
  ar_ = aor_ * aor_;
  br_ = bt_ = bp_ = bpp_ = 0.0;
  for (int n = 1; n <= GEO_MAG_MAX_DEG; n++)
  {
    ar_ = ar_ * aor_;
    for (int m = 0, D4 = (n + m + 1); D4 > 0; D4--, m += 1)
    {
      // COMPUTE UNNORMALIZED ASSOCIATED LEGENDRE POLYNOMIALS
      // AND DERIVATIVES VIA RECURSION RELATIONS
      if (altkm != oalt_ || dlat != olat_)
      {
        if (n == m)
        {
          *(p + n + m * 13) = st_ * *(p + n - 1 + (m - 1) * 13);
          dp_[m][n] = st_ * dp_[m - 1][n - 1] + ct_ * *(p + n - 1 + (m - 1) * 13);
          goto S50;
          // TODO: break ?
        }
        if (n == 1 && m == 0)
        {
          *(p + n + m * 13) = ct_ * *(p + n - 1 + m * 13);
          dp_[m][n] = ct_ * dp_[m][n - 1] - st_ * *(p + n - 1 + m * 13);
          goto S50;
          // TODO: break ?
        }
        if (n > 1 && n != m)
        {
          if (m > n - 2) *(p + n - 2 + m * 13) = 0.0;
          if (m > n - 2) dp_[m][n - 2] = 0.0;
          *(p + n + m * 13) = ct_ * *(p + n - 1 + m * 13) - K_COEFF[m][n] * *(p + n - 2 + m * 13);
          dp_[m][n] = ct_ * dp_[m][n - 1] - st_ * *(p + n - 1 + m * 13) - K_COEFF[m][n] * dp_[m][n - 2];
        }
      }

    S50:
      // TIME ADJUST THE GAUSS COEFFICIENTS
      if (time != otime_ || oyear_ != epochYear_)
      {
        switch (epochYear_)
        {
        case 1985:
          tc_[m][n] = WMM1985_C[m][n] + dt * WMM1985_CD[m][n];
          if (m != 0)
            tc_[n][m - 1] = WMM1985_C[n][m - 1] + dt * WMM1985_CD[n][m - 1];
          break;

        case 1990:
          tc_[m][n] = WMM1990_C[m][n] + dt * WMM1990_CD[m][n];
          if (m != 0)
            tc_[n][m - 1] = WMM1990_C[n][m - 1] + dt * WMM1990_CD[n][m - 1];
          break;

        case 1995:
          tc_[m][n] = WMM1995_C[m][n] + dt * WMM1995_CD[m][n];
          if (m != 0)
            tc_[n][m - 1] = WMM1995_C[n][m - 1] + dt * WMM1995_CD[n][m - 1];
          break;

        case 2000:
          tc_[m][n] = WMM2000_C[m][n] + dt * WMM2000_CD[m][n];
          if (m != 0)
            tc_[n][m - 1] = WMM2000_C[n][m - 1] + dt * WMM2000_CD[n][m - 1];
          break;

        case 2005:
          tc_[m][n] = WMM2005_C[m][n] + dt * WMM2005_CD[m][n];
          if (m != 0)
            tc_[n][m - 1] = WMM2005_C[n][m - 1] + dt * WMM2005_CD[n][m - 1];
          break;

        case 2010:
          tc_[m][n] = WMM2010_C[m][n] + dt * WMM2010_CD[m][n];
          if (m != 0)
            tc_[n][m - 1] = WMM2010_C[n][m - 1] + dt * WMM2010_CD[n][m - 1];
          break;

        case 2015:
          tc_[m][n] = WMM2015_C[m][n] + dt * WMM2015_CD[m][n];
          if (m != 0)
            tc_[n][m - 1] = WMM2015_C[n][m - 1] + dt * WMM2015_CD[n][m - 1];
          break;

        default:
        case 2020:
          tc_[m][n] = WMM2020_C[m][n] + dt * WMM2020_CD[m][n];
          if (m != 0)
            tc_[n][m - 1] = WMM2020_C[n][m - 1] + dt * WMM2020_CD[n][m - 1];
          break;
        }
      }

      // ACCUMULATE TERMS OF THE SPHERICAL HARMONIC EXPANSIONS
      double temp1, temp2;
      double par = ar_ * *(p + n + m * 13);
      if (m == 0)
      {
        temp1 = tc_[m][n] * cp_[m];
        temp2 = tc_[m][n] * sp_[m];
      }
      else
      {
        temp1 = tc_[m][n] * cp_[m] + tc_[n][m - 1] * sp_[m];
        temp2 = tc_[m][n] * sp_[m] - tc_[n][m - 1] * cp_[m];
      }
      bt_ = bt_ - ar_ * temp1 * dp_[m][n];
      bp_ += (FM_COEFF[m] * temp2 * par);
      br_ += (FN_COEFF[n] * temp1 * par);

      // SPECIAL CASE:  NORTH/SOUTH GEOGRAPHIC POLES
      if (st_ == 0.0 && m == 1)
      {
        pp_[n] = (n == 1) ? pp_[n - 1] : (ct_ * pp_[n - 1] - K_COEFF[m][n] * pp_[n - 2]);
        double parp = ar_ * pp_[n];
        bpp_ += (FM_COEFF[m] * temp2 * parp);
      }
    }
  }

  bp_ = (st_ == 0.0) ? bpp_ : bp_ / st_;

  // ROTATE MAGNETIC VECTOR COMPONENTS FROM SPHERICAL TO
  // GEODETIC COORDINATES, COMPUTE DECLINATION (DEC);
  dec_ = atan2(bp_, (-bt_ * ca_ - br_ * sa_));

  // set current values for this call
  oyear_ = epochYear_;
  otime_ = time;
  oalt_ = altkm;
  olat_ = dlat;
  olon_ = dlon;

  variance = dec_;
  return 0;
}

////////////////////////////////////////////////////////////////

WorldMagneticModel::WorldMagneticModel()
  : geomag_(new GeoMag)
{
}

WorldMagneticModel::~WorldMagneticModel()
{
  delete geomag_;
  geomag_ = NULL;
}

int WorldMagneticModel::calculateMagneticVariance(const simCore::Vec3& lla, int ordinalDay, int year, double& varianceRad)
{
  if (geomag_ == NULL)
    return 1;
  return geomag_->calculateVariance(lla, ordinalDay, year, varianceRad);
}

int WorldMagneticModel::calculateMagneticVariance(const simCore::Vec3& lla, const simCore::TimeStamp& timeStamp, double& varianceRad)
{
  return calculateMagneticVariance(lla, static_cast<int>(timeStamp.secondsSinceRefYear().Double() / SECPERDAY), timeStamp.referenceYear(), varianceRad);
}

int WorldMagneticModel::calculateMagneticBearing(const simCore::Vec3& lla, const simCore::TimeStamp& timeStamp, double& bearingRad)
{
  double variance = 0.0;
  if (calculateMagneticVariance(lla, timeStamp, variance) == 0)
  {
    bearingRad = simCore::angFix2PI(bearingRad - variance);
    return 0;
  }
  return 1;
}

int WorldMagneticModel::calculateTrueBearing(const simCore::Vec3& lla, const simCore::TimeStamp& timeStamp, double& bearingRad)
{
  double variance = 0.0;
  if (calculateMagneticVariance(lla, timeStamp, variance) == 0)
  {
    bearingRad = simCore::angFix2PI(bearingRad + variance);
    return 0;
  }
  return 1;
}

}
